﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace DDD.Core {
    /// <summary>
    /// 2要素のベクトル構造体
    /// </summary>
    /// <remarks>
    /// アニメート可能､エクスポート可能
    /// </remarks>
    /// <typeparam name="T">数値型</typeparam>
    public struct Vector2<T> : IExportable<T>, IAnimatable<T>, IEquatable<Vector2<T>> where T : struct {

        #region Constructor
        /// <summary>
        /// XY要素を指定して<see cref="Vector2{T}"/>オブジェクトを作成するコンストラクター
        /// </summary>
        /// <remarks>
        /// 正規化フラグはたちません｡
        /// (メモ) <typeparamref name="T"/>が数値型である事を内部でチェックすべき?
        /// 今は構造体であれば何でもOK
        /// </remarks>
        /// <param name="x">X要素</param>
        /// <param name="y">Y要素</param>
        public Vector2 (T x, T y)
            : this () {
            this.X = x;
            this.Y = y;
            this.IsNormalized = false;
           
        }

        /// <summary>
        /// XY要素を配列で指定して<see cref="Vector2{T}"/>オブジェクトを作成するコンストラクター
        /// </summary>
        /// <remarks>
        /// 正規化フラグは立ちません｡
        /// </remarks>
        /// <param name="xy">XY要素</param>
        public Vector2 (T[] xy)
            : this () {
            if (xy == null) {
                throw new ArgumentNullException ("XY is null");
            }
            if (xy.Length < 2) {
                throw new ArgumentException ("XY is too short");
            }
            this.X = xy[0];
            this.Y = xy[1];
            this.IsNormalized = false;
        }
        #endregion

        #region Property
        /// <summary>
        /// X要素
        /// </summary>
        public T X {
            get;
            private set;
        }

        /// <summary>
        /// Y要素
        /// </summary>
        public T Y {
            get;
            private set;
        }

        /// <summary>
        /// 正規化済みかどうかのフラグ
        /// </summary>
        public bool IsNormalized {
            get;
            private set;
        }



        /// <summary>
        /// コンポーネント数
        /// </summary>
        public int ComponentCount {
            get { return 2; }
        }

        /// <summary>
        /// コンポーネントにアクセスするインデクサー
        /// </summary>
        /// <param name="index">インデックス</param>
        /// <returns>ベクトルの構成要素</returns>
        public T this[int index] {
            get {
                if (index < 0 || index >= ComponentCount) {
                    throw new IndexOutOfRangeException ("Index is out of range");
                }
                switch (index) {
                    case 0: return X;
                    case 1: return Y;
                    default: return default(T);
                }
            }
            set {
                if (index < 0 || index >= ComponentCount) {
                    throw new IndexOutOfRangeException ("Index is out of range");
                }
                switch (index) {
                    case 0: X = value; break;
                    case 1: Y = value; break;
                    default: break;
                }
            }
        }

        /// <summary>
        /// ベクトルの長さ
        /// </summary>
        public T Length {
            get { return (T)Math.Sqrt ((dynamic)X * X + (dynamic)Y * Y); }
        }

        /// <summary>
        /// ベクトルの長さの2乗
        /// </summary>
        public T Length2 {
            get { return ((dynamic)X * X + (dynamic)Y * Y); }
        }

        #endregion

        #region Method
        /// <summary>
        /// このベクトルを正規化した新しいベクトルを作成します
        /// </summary>
        /// <returns>作成した正規化済みのベクトル</returns>
        public Vector2<T> Normalize () {
            if (IsNormalized) {
                return this;
            }

            var v = new Vector2<T> ((dynamic)X / Length, (dynamic)Y / Length);
            v.IsNormalized = true;
            return v;
        }

        /// <summary>
        /// ベクトルとfloatのかけ算
        /// </summary>
        /// <remarks>
        /// コンポーネント要素毎に指定のfloat値をかけます。
        /// </remarks>
        /// <param name="v">ベクトル</param>
        /// <param name="f">floatの値</param>
        /// <returns></returns>
        public static Vector2<T> operator * (Vector2<T> v, float f) {
            return new Vector2<T> ((dynamic)v.X * f, (dynamic)v.Y * f);
        }

        /// <summary>
        /// floatとベクトルのかけ算
        /// </summary>
        /// <remarks>
        /// コンポーネント要素毎に指定のfloatの値をかけます。
        /// </remarks>
        /// <param name="f">floatの値</param>
        /// <param name="v">ベクトル</param>
        /// <returns></returns>
        public static Vector2<T> operator * (float f, Vector2<T> v) {
            return v * f;
        }

        /// <summary>
        /// ベクトルとfloatの割り算
        /// </summary>
        /// <remarks>
        /// コンポーネント要素毎に指定のfloat値で割ります。
        /// </remarks>
        /// <param name="v">ベクトル</param>
        /// <param name="f">floatの値</param>
        /// <returns></returns>
        public static Vector2<T> operator / (Vector2<T> v, float f) {
            if (f == 0) {
                throw new ArgumentException ("Divided by 0");
            }
            return new Vector2<T> ((dynamic)v.X / f, (dynamic)v.Y / f);
        }

        /// <summary>
        /// ベクトル同士の足し算
        /// </summary>
        /// <remarks>
        /// 2つのベクトル<paramref name="v1"/>,<paramref name="v2"/>をコンポーネント要素毎に足します。
        /// </remarks>
        /// <param name="v1">ベクトル1</param>
        /// <param name="v2">ベクトル2</param>
        /// <returns>ベクトル</returns>
        public static Vector2<T> operator + (Vector2<T> v1, Vector2<T> v2) {
            return new Vector2<T> ((dynamic)v1.X + v2.X, (dynamic)v1.Y + v2.Y);
        }

        /// <summary>
        /// ベクトル同士の引き算
        /// </summary>
        /// <remarks>
        /// 2つのベクトル<paramref name="v1"/>,<paramref name="v2"/>をコンポーネント要素毎に引きます。
        /// </remarks>
        /// <param name="v1">ベクトル1</param>
        /// <param name="v2">ベクトル2</param>
        /// <returns></returns>
        public static Vector2<T> operator - (Vector2<T> v1, Vector2<T> v2) {
            return new Vector2<T> ((dynamic)v1.X - v2.X, (dynamic)v1.Y - v2.Y);
        }

        /// <summary>
        /// ベクトルを反転します
        /// </summary>
        /// コンポーネント要素全てにマイナスをかけます。
        /// <param name="v">ベクトル</param>
        /// <returns></returns>
        public static Vector2<T> operator - (Vector2<T> v) {
            return new Vector2<T> (-(dynamic)v.X, -(dynamic)v.Y);
        }


        /// <summary>
        /// 上方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector2<T> Up  {
            get{return new Vector2<T> ((dynamic)0, (dynamic)1);}
        }

        /// <summary>
        /// 下方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector2<T> Down {
            get { return new Vector2<T> ((dynamic)0, (dynamic)(-1)); }
        }

        /// <summary>
        /// 右方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector2<T> Right {
            get { return new Vector2<T> ((dynamic)1, (dynamic)0); }
        }

        /// <summary>
        /// 左方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector2<T> Left {
            get { return new Vector2<T> ((dynamic)(-1), (dynamic)0); }
        }

        /// <summary>
        /// 全要素をfloatに変換して取り出します
        /// </summary>
        /// <param name="v">変換元のベクトル</param>
        /// <returns>ベクトルの構成要素</returns>
        public static explicit operator T[] (Vector2<T> v) {
            return new T[] { v[0], v[1] };
        }

        /// <summary>
        /// 同値性を比較します
        /// </summary>
        /// <remarks>
        /// 同値性を比較する汎用の比較関数です。
        /// <see cref="Vector2{T}"/>オブジェクトはコンポーネント要素が厳密に一致しなくても、
        /// 全てのコンポーネント要素で差が<see cref="GlobalSettings.Tolerance"/>以下であれば等しいと見なされます。
        /// </remarks>
        /// <param name="obj">右辺のオブジェクト</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public override bool Equals (object obj) {
            if (object.ReferenceEquals (obj, null)) {
                return false;
            }
            if (object.ReferenceEquals (obj, this)) {
                return true;
            }
            var v = obj as Vector2<T>?;
            if (v == null) {
                return false;
            }

            return this.Equals (v.Value);
        }

        /// <summary>
        /// タイプセーフな同値性を比較します
        /// </summary>
        /// <remarks>
        /// 
        /// </remarks>
        /// <param name="v">右辺の<see cref="Vector2{T}"/>オブジェクト</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public bool Equals (Vector2<T> v) {
            return (Math.Abs ((dynamic)X - v.X) < GlobalSettings.Tolerance) &&
                    (Math.Abs ((dynamic)Y - v.Y) < GlobalSettings.Tolerance);
        }

        /// <summary>
        /// 等号による同値性を比較します
        /// </summary>
        /// <param name="left">左項</param>
        /// <param name="right">右項</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public static bool operator == (Vector2<T> left, Vector2<T> right) {
            return left.Equals (right);
        }

        /// <summary>
        /// 等号による同値性を比較します
        /// </summary>
        /// <param name="left">左項</param>
        /// <param name="right">右項</param>
        /// <returns>等しくないときtrue, そうでないときfalse</returns>
        public static bool operator != (Vector2<T> left, Vector2<T> right) {
            return !(left == right);
        }

        /// <summary>
        /// 一意のハッシュ値を取得します
        /// </summary>
        /// <remarks>
        /// このメソッドは同値性の比較メソッドと異なり
        /// <see cref="GlobalSettings.Tolerance"/>の影響を受けません。
        /// </remarks>
        /// <returns></returns>
        public override int GetHashCode () {
            return base.GetHashCode ();
        }


        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary> 
        public override string ToString () {
            return string.Format ("Vector2: X={0}, Y={1}", X, Y);
        }

        #endregion


    }

 
}
